package com.angle3d;

import android.opengl.Matrix;

/**
 * Class representing a 4x4 matrix.
 * A 4x4 matrix can be used to represent transformations and/or perspective/orthographic projections.
 * 
 * @author Alessandro Boccalatte
 *
 */
public class Matrix4 {

	private float[] mValues = new float[16];
	
	/**
	 * Creates a 4x4 matrix from a given array of floats
	 * @param values an array of 16 floats
	 */
	public Matrix4(float[] values) {
		System.arraycopy(values, 0, mValues, 0, 16);
	}
	
	/**
	 * Creates a default 4x4 matrix as an Identity matrix (1 where row = column, 0 elsewhere)
	 */
	public Matrix4() {
		float[] values = new float[16];
		Matrix.setIdentityM(values, 0);
		System.arraycopy(values, 0, mValues, 0, 16);
	}
	
	/**
	 * Creates a 4x4 matrix from a given matrix (copying its values).
	 * @param matrix 
	 */
	public Matrix4(Matrix4 matrix) {
		this(matrix.getValues());
	}
	
	/**
	 * Returns an array of 16 float values representing the matrix values.
	 * @return an array of 16 float values representing the matrix values.
	 */
	public float[] getValues() {
		float[] values = new float[16];
		System.arraycopy(mValues, 0, values, 0, 16);
		return values;
	}
	
	/**
	 * Sets the matrix internal values from a given array of float values.
	 * @param values an array of 16 float values.
	 */
	public void setValues(float[] values) {
		System.arraycopy(values, 0, mValues, 0, 16);
	}
	
	/**
	 * Post-multiply this matrix by a given matrix.
	 * @param matrix a matrix 
	 * @see #preMultiply(Matrix4)
	 */
	public void postMultiply(Matrix4 matrix) {
		float[] result = new float[16];
		Matrix.multiplyMM(result, 0, mValues, 0, matrix.getValues(), 0);
		setValues(result);
	}
	
	/**
	 * Pre-multiply this matrix by a given matrix.
	 * @param matrix a matrix 
	 * @see #postMultiply(Matrix4)
	 */
	public void preMultiply(Matrix4 matrix) {
		float[] result = new float[16];
		Matrix.multiplyMM(result, 0, matrix.getValues(), 0, mValues, 0);
		setValues(result);
	}
	
	/**
	 * Sets this matrix as an identity matrix.
	 */
	public void setIdentity() {
		Matrix.setIdentityM(mValues, 0);
	}
	
	/**
	 * Applies a translation in 3D space to this matrix.
	 * @param x the X component of the translation vector
	 * @param y the Y component of the translation vector
	 * @param z the Z component of the translation vector
	 * @return a matrix representing a translation.
	 */
	public void translate(float x, float y, float z) {
		Matrix.translateM(mValues, 0, x, y, z);
	}
	
	/**
	 * Applies a rotation of a given angle and axis to this matrix.
	 * @param angle the rotation angle (in radians)
	 * @param x the X component of the rotation axis
	 * @param y the Y component of the rotation axis
	 * @param z the Z component of the rotation axis
	 */
	public void rotate(float angle, float x, float y, float z) {
		Matrix.rotateM(mValues, 0, (float)Math.toDegrees(angle), x, y, z);
	}
	
	/**
	 * Applies a scale operation to this matrix.
	 * @param x the X component of the scale vector
	 * @param y the Y component of the scale vector
	 * @param z the Z component of the scale vector
	 */
	public void scale(float x, float y, float z) {
		Matrix.scaleM(mValues, 0, x, y, z);
	}
	
	/**
	 * Sets this matrix as a billboard (i.e. removing any rotation component, while keeping translation and scale)
	 */
	public void setBillboarded()
	{
		float[] scale = new float[]{1,1,1};
		getScale(scale);
		for (int row = 0; row < 3; ++row)
		{
			for (int col = 0; col < 3; ++col)
			{
				mValues[4*row + col] = (col==row) ? scale[row] : 0;
			}
		}
	}
	
	private void getScale(float[] scale)
	{
		scale[0] = (float)Math.sqrt(mValues[0]*mValues[0] + mValues[1]*mValues[1] + mValues[2]*mValues[2]);
		scale[1] = (float)Math.sqrt(mValues[4]*mValues[4] + mValues[5]*mValues[5] + mValues[6]*mValues[6]);
		scale[2] = (float)Math.sqrt(mValues[8]*mValues[8] + mValues[9]*mValues[9] + mValues[10]*mValues[10]);
	}
	
	/**
	 * Sets this matrix as a projection matrix of a given frustum.
	 * @param left
	 * @param right
	 * @param bottom
	 * @param top
	 * @param near
	 * @param far
	 */
	public void setFrustum(float left, float right, float bottom, float top, float near, float far) {
		Matrix.frustumM(mValues, 0, left, right, bottom, top, near, far);
	}
	
	/**
	 * Sets this matrix as a perspective projection matrix with the given perspective parameters. 
	 * @param fov the vertical field of view angle (in radians)
	 * @param aspect the aspect ratio
	 * @param near the near clip distance
	 * @param far the far clip distance
	 */
	public void setPerspective(float fov, float aspect, float near, float far) {
		float top = near*(float)Math.tan(0.5f*fov);
		float bottom = -top;
		float right = aspect*top;
		float left = -right;
		setFrustum(left, right, bottom, top, near, far);
	}
	
	/**
	 * Sets this matrix as a lookAt matrix.
	 * @param eyeX
	 * @param eyeY
	 * @param eyeZ
	 * @param centerX
	 * @param centerY
	 * @param centerZ
	 * @param upX
	 * @param upY
	 * @param upZ
	 */
	public void setLookAt(float eyeX, float eyeY, float eyeZ, 
						  float centerX, float centerY, float centerZ, 
						  float upX, float upY, float upZ) {
		Matrix.setLookAtM(mValues, 0, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
	}
	
	//----------static methods------------//
	//-----------------------------------_//
	/**
	 * Multiply two matrices.
	 * @param left the left matrix
	 * @param right the right matrix
	 * @return the result of the multiplication.
	 */
	public static Matrix4 multiply(Matrix4 left, Matrix4 right) {
		float[] result = new float[16];
		Matrix.multiplyMM(result, 0, left.getValues(), 0, right.getValues(), 0);
		return new Matrix4(result);
	}
	
	/**
	 * Creates and returns an identity matrix.
	 * @return an identity matrix.
	 */
	public static Matrix4 identity() {
		Matrix4 result = new Matrix4();
		result.setIdentity();
		return result;
	}
	

	/**
	 * Creates and returns a matrix representing a translation.
	 * @param x the X component of the translation vector
	 * @param y the Y component of the translation vector
	 * @param z the Z component of the translation vector
	 * @return a matrix representing a translation.
	 */
	public static Matrix4 translation(float x, float y, float z) {
		Matrix4 result = Matrix4.identity();
		result.translate(x, y, z);
		return result;
	}
	
	/**
	 * Creates and returns a matrix representing a rotation.
	 * @param angle the rotation angle (in radians)
	 * @param x the X component of the rotation axis
	 * @param y the Y component of the rotation axis
	 * @param z the Z component of the rotation axis
	 * @return a matrix representing a rotation.
	 */
	public static Matrix4 rotation(float angle, float x, float y, float z) {
		Matrix4 result = Matrix4.identity();
		result.rotate(angle, x, y, z);
		return result;
	}
	
	/**
	 * Creates and returns a matrix representing a scaling.
	 * @param x the X component of the scale vector
	 * @param y the Y component of the scale vector
	 * @param z the Z component of the scale vector
	 * @return a matrix representing a scaling.
	 */
	public static Matrix4 scaling(float x, float y, float z) {
		Matrix4 result = Matrix4.identity();
		result.scale(x, y, z);
		return result;
	}
}

